﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using QTPlugin;
using QTPlugin.Interop;
using Kawanon.Usability;

namespace Kawanon.QtHoney
{
	public class NListView : NativeWindow
	{
		private const uint WM_LBUTTONDOWN = 0x0201;
		private const uint WM_LBUTTONUP = 0x0202;
		private const uint WM_RBUTTONDOWN = 0x0204;
		private const uint WM_RBUTTONUP = 0x0205;
		private const uint WM_MOUSEMOVE = 0x0200;
		private const uint WM_CAPTURECHANGED = 0x0215;
		private const uint WM_CONTEXTMENU = 0x007B;
		private const uint WM_TIMER = 0x0113;
		private const uint WM_CLOSE = 0x0010;
		private const uint WM_DESTROY = 0x0002;
		private const uint WM_USER = 0x0400;

		private const uint SB_SIMPLE = (WM_USER + 9);
		private const uint SB_SETTEXTW = (WM_USER + 11);

		private const uint SBT_NOBORDERS = 0x0100;

		private const uint LVM_FIRST = 0x1000;
		private const uint LVM_GETNEXTITEM = (LVM_FIRST + 12);
		private const uint LVM_HITTEST = (LVM_FIRST + 18);
		private const uint LVM_SETITEMSTATE = (LVM_FIRST + 43);
		private const uint LVM_GETITEMSTATE = (LVM_FIRST + 44);
		private const uint LVM_GETSELECTEDCOUNT = 0x1032;

		private const int LVIF_TEXT = 0x0001;
		private const int LVIF_IMAGE = 0x0002;
		private const int LVIF_PARAM = 0x0004;
		private const int LVIF_STATE = 0x0008;

		private const int LVIS_FOCUSED = 0x0001;
		private const int LVIS_SELECTED = 0x0002;

		private const uint LVHT_NOWHERE = 0x0001;
		private const uint LVHT_ONITEMICON = 0x0002;
		private const uint LVHT_ONITEMLABEL = 0x0004;
		private const uint LVHT_ONITEMSTATEICON = 0x0008;
		private const uint LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON);

		private const uint LVHT_ABOVE = 0x0008;
		private const uint LVHT_BELOW = 0x0010;
		private const uint LVHT_TORIGHT = 0x0020;
		private const uint LVHT_TOLEFT = 0x0040;

		private const uint LVNI_ALL = 0x0000;
		private const uint LVNI_FOCUSED = 0x0001;
		private const uint LVNI_SELECTED = 0x0002;

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int nItem, ref LVITEM plvi);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr zero, ref LVHITTESTINFO phi);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, bool wParam, IntPtr lParam);

		// SB_SETTEXTW
		[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, string lParam);

		[DllImport("user32.dll")]
		static extern IntPtr SetCapture(IntPtr hWnd);

		[DllImport("user32.dll")]
		static extern IntPtr GetCapture();

		[DllImport("user32.dll")]
		static extern bool ReleaseCapture();

		[DllImport("user32.dll")]
		static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		struct LVITEM
		{
			public uint mask;
			public int iItem;
			public int iSubItem;
			public uint state;
			public uint stateMask;
			public IntPtr pszText;
			public int cchTextMax;
			public int iImage;
			public int lParam;
			public int iIndent;
			// (_WIN32_WINNT >= 0x501)
			public int iGroupId;
			public uint cColumns;
			public IntPtr puColumns;
		}

		[StructLayout(LayoutKind.Sequential)]
		struct LVHITTESTINFO
		{
			public POINT pt;
			public uint flags;
			public int iItem;
			public int iSubItem;
		}

		public IPluginServer Server { get; set; }
		public IntPtr StatusBarHandle { get; set; }

		private GestureProvider m_gesture = new GestureProvider();

		public NListView()
		{
			m_gesture.GestureDetected += new GestureEventHandler(GestureProvider_GestureDetected);
			m_gesture.TrailDetected += new GestureEventHandler(GestureProvider_TrailDetected);
			m_gesture.RightClick += new MouseEventHandler(GestureProvider_RightClick);
			m_gesture.Expressions = GestureSettings.LoadAvailableExpressions();
			m_gesture.AllowUnknownExpression = true;
		}

		void GestureProvider_GestureDetected(object sender, GestureEventArgs e)
		{
			try
			{
				Execute(e.Expression);
			}
			catch (NotImplementedException)
			{
			}

			if (StatusBarHandle != IntPtr.Zero)
			{
				SendMessage(StatusBarHandle, SB_SIMPLE, false, IntPtr.Zero);
			}
		}

		void GestureProvider_TrailDetected(object sender, GestureEventArgs e)
		{
			if (StatusBarHandle != IntPtr.Zero)
			{
				List<GestureExpression> list = new List<GestureExpression>(e.Suggestions);
				GestureExpression equivalent = GestureSettings.FindExpression(list, e.Expression.Trails);
				if (equivalent != null)	// 候補の中に完全にマッチするものがあれば、先頭に表示する
				{
					list.Remove(equivalent);
					list.Insert(0, equivalent);
				}

				List<string> names = list.ConvertAll(delegate(GestureExpression expr)
				{
					return expr.Name;
				});
				string text = String.Format("{0}  ({1})",
					e.Expression.FormatTrails(),
					(names.Count > 0) ? String.Join(", ", names.ToArray()) : "なし");

				SendMessage(StatusBarHandle, SB_SIMPLE, true, IntPtr.Zero);

				if (SendMessage(StatusBarHandle, SB_SETTEXTW, (255 | SBT_NOBORDERS), text) == IntPtr.Zero)
				{
					Dbg.Abort("SB_SETTEXTW Failed");
				}
			}
		}

		private IntPtr PointToDword(Point pt)
		{
			return (IntPtr)((pt.X & 0xffff) | ((pt.Y & 0xffff) << 16));
		}

		private int GetNextItem(int i, uint state)
		{
			return (int)SendMessage(this.Handle, LVM_GETNEXTITEM, new IntPtr(i), new IntPtr(state));
		}

		// 右クリックをエミュレート
		void GestureProvider_RightClick(object sender, MouseEventArgs e)
		{
			int i = GetNextItem(-1, LVNI_SELECTED);
			while (i != -1)	// 最初にすべての選択を解除する
			{
				LVITEM lvi = new LVITEM();
				lvi.state = 0;
				lvi.stateMask = LVIS_SELECTED;
				if (SendMessage(this.Handle, LVM_SETITEMSTATE, i, ref lvi) == IntPtr.Zero)
				{
					Dbg.Abort("LVM_SETITEMSTATE 1");
				}

				i = GetNextItem(i, LVNI_SELECTED);
			}

			//int count = (int)SendMessage(this.Handle, LVM_GETSELECTEDCOUNT, IntPtr.Zero, IntPtr.Zero);
			//if (count != 0)
			//{
			//    return;
			//}
			POINT pt = new POINT(e.Location);
			ScreenToClient(this.Handle, ref pt);
			LVHITTESTINFO hi = new LVHITTESTINFO();
			hi.pt = pt;
			if ((int)SendMessage(this.Handle, LVM_HITTEST, IntPtr.Zero, ref hi) != -1 && (hi.flags & LVHT_ONITEM) != 0)
			{
				LVITEM lvi = new LVITEM();
				lvi.state = LVIS_FOCUSED | LVIS_SELECTED;
				lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
				if (SendMessage(this.Handle, LVM_SETITEMSTATE, hi.iItem, ref lvi) == IntPtr.Zero)
				{
					Dbg.Abort("LVM_SETITEMSTATE 2");
				}
			}

			Message m = Message.Create(this.Handle, (int)WM_CONTEXTMENU, this.Handle, PointToDword(e.Location));
			base.WndProc(ref m);
		}

		private void Execute(GestureExpression expr)
		{
			Action.Arguments args = new Action.Arguments();
			args.Server = Server;
			args.Plugin = null;

			if (Server != null)
			{
				expr.Action.Execute(args);
			}
			else
			{
				MessageBox.Show(this, String.Format("{0} {1}", expr.Name, expr.FormatTrails()));
			}
		}

		private bool IsItemDragStarted(Point ptScreen)
		{
			int count = (int)SendMessage(this.Handle, LVM_GETSELECTEDCOUNT, IntPtr.Zero, IntPtr.Zero);
			if (count == 0)
			{
				return false;
			}

			POINT pt = new POINT(ptScreen);
			ScreenToClient(this.Handle, ref pt);
			LVHITTESTINFO hi = new LVHITTESTINFO();
			hi.pt = pt;
			if ((int)SendMessage(this.Handle, LVM_HITTEST, IntPtr.Zero, ref hi) == -1 || (hi.flags & LVHT_ONITEM) == 0)
			{
				return false;
			}

			if ((int)SendMessage(this.Handle, LVM_GETITEMSTATE, (IntPtr)hi.iItem, (IntPtr)LVIS_SELECTED) != LVIS_SELECTED)
			{
				return false;
			}

			return true;
		}

		protected override void WndProc(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				case WM_RBUTTONDOWN:
					if (IsItemDragStarted(Cursor.Position))
					{
						base.WndProc(ref m);
						return;
					}
					break;

				//case WM_TIMER:
				//    GestureTimerProc(this.Handle, 0, IntPtr.Zero, 0);
				//    break;
				case WM_DESTROY:
					//DebugHelpers.Play();
					//Console.WriteLine("Destroy Window");

					ReleaseHandle();
					break;
			}
			if (m_gesture.ProcessMessage(ref m))
			{
				return;
			}
			base.WndProc(ref m);
		}
	}
}
